package cn.ecommerce.course.module.users.filter;

import cn.ecommerce.course.module.users.controller.app.users.vo.SecurityUserVO;
import cn.ecommerce.course.module.users.dal.dataobject.jwt.JwtPayload;
import cn.ecommerce.course.module.users.dal.dataobject.users.UsersDO;
import cn.ecommerce.course.module.users.utils.JwtUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

import static cn.ecommerce.course.module.users.utils.RedisConstants.LOGIN_USER_KEY;

/**
 * JWT 认证过滤器（核心安全组件）
 *
 * 定位：Spring Security 过滤器链的前置过滤器（执行顺序在 UsernamePasswordAuthenticationFilter 之前）
 * 核心职责：
 * 1. 提取请求中的 JWT 令牌，完成格式校验、签名验证、过期判断
 * 2. 解析令牌载荷（Payload），构造 Spring Security 认可的认证对象（Authentication）
 * 3. 将认证信息写入 SecurityContext（ThreadLocal 容器），为后续授权逻辑提供依据
 * 4. 对无效/缺失令牌的请求“安全放行”，交由后续过滤器统一处理（避免响应格式混乱）
 *
 * 设计原则：
 * - 无状态：不依赖服务端 Session，完全基于请求头中的 JWT 令牌实现认证
 * - 解耦：仅负责“认证信息注入”，不处理授权规则（授权由 SecurityFilterChain 配置）、不直接返回错误响应（由后续链统一处理）
 * - 容错：对各种异常场景（无令牌、格式错误、签名失效、过期）均做兼容，避免过滤器抛出未捕获异常导致请求中断
 *
 * 依赖组件：
 * - JwtUtils：自定义 JWT 工具类，提供令牌校验、解析功能
 * - SecurityUserVO：Spring Security 用户信息封装类，包含用户基础信息和权限集合
 * - JwtPayload：JWT 载荷数据模型，存储令牌中携带的核心信息（如用户名、过期时间、用户ID等）
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    /**
     * JWT 工具类注入（负责令牌的校验、解析逻辑）
     * 注：通过 @Resource 注入而非 @Autowired，优先按名称匹配，避免多实现类时的注入冲突
     */
    @Resource
    private JwtUtils jwtUtils;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * JWT 认证过滤主流程（核心方法）
     * 特性：继承 OncePerRequestFilter，确保每个 HTTP 请求仅被过滤一次（避免因请求转发/包含导致重复执行）
     *
     * @param request  HttpServletRequest：当前请求对象，用于提取请求头（Authorization）
     * @param response HttpServletResponse：当前响应对象，本过滤器暂不直接修改响应（避免提前写入响应流导致后续组件无法操作）
     * @param filterChain FilterChain：过滤器链，用于将请求传递到下一个过滤器（或业务控制器）
     * @throws ServletException 过滤器执行中的 Servlet 标准异常（如请求解析失败）
     * @throws IOException 输入输出异常（如请求流读取失败）
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // ===================================== Step 1：提取请求头中的 Authorization 信息 =====================================
        // 约定：前端需按 RFC 6750 标准传递令牌，格式为 "Bearer <token>"（Bearer 后接一个英文空格，再跟令牌字符串）
        // 示例：Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzE2NDIwODAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
        String authorizationHeader = request.getHeader("Authorization");

        // ===================================== Step 2：令牌格式初筛（快速排除无效请求） =====================================
        // 初筛逻辑：必须同时满足两个条件，否则判定为“无有效令牌请求”
        // 1. StringUtils.hasText(authorizationHeader)：排除 null、空字符串、纯空白字符（如 "   "）的情况
        //    - 原因：避免后续 substring 操作抛出 StringIndexOutOfBoundsException
        // 2. authorizationHeader.startsWith("Bearer ")：严格匹配 Bearer 认证方案（区分大小写）
        //    - 原因：防止误解析其他认证方案的头部（如 Basic 认证的 "Basic dXNlcjpwYXNzd29yZA=="）
        if (!StringUtils.hasText(authorizationHeader) || !authorizationHeader.startsWith("Bearer ")) {
            // 处理逻辑：无有效令牌，直接放行到下一个过滤器
            // 后续流程：若请求访问的是需认证接口，Security 会自动返回 401 Unauthorized；若为匿名接口则正常访问
            filterChain.doFilter(request, response);
            return;
        }

        // ===================================== Step 3：令牌提取与有效性校验 =====================================
        // 3.1 提取纯令牌字符串：去掉 "Bearer " 前缀（前缀长度固定为 7，包含末尾的空格）
        // 风险点：若前端传递的前缀后无空格（如 "Bearerxxx"），此处 substring 会导致令牌解析失败，后续校验会拦截
        String jwtToken = authorizationHeader.substring(7);

        // 从Redis获取token,用于做jwt黑名单，拦截
        String token = stringRedisTemplate.opsForValue().get(LOGIN_USER_KEY + jwtToken);
        if (token == null){
            filterChain.doFilter(request, response);
            return;
        }

        // 3.2 调用 JwtUtils 校验令牌有效性（核心校验逻辑）
        // JwtUtils.verifyToken() 需完成的校验：
        // - 签名校验：确保令牌未被篡改（使用预共享密钥/公钥验证签名）
        // - 过期校验：判断令牌的 exp（过期时间）字段是否早于当前时间
        // - 格式校验：确保令牌是合法的 JWT 结构（Header.Payload.Signature）
        // - 自定义校验（可选）：如令牌是否在黑名单中、iss（签发者）是否合法等
        // 返回值：校验通过则返回解析后的 JwtPayload 对象；校验失败则返回 null
        JwtPayload jwtPayload = jwtUtils.verifyToken(jwtToken);

        // 3.3 校验失败处理：若 jwtPayload 为 null，说明令牌无效（过期/篡改/非法格式）
        if (jwtPayload == null) {
            // 处理逻辑：不直接返回 401，而是放行到后续过滤器
            // 原因：统一响应格式（由全局异常处理器或 Security 内置过滤器返回标准化 401 响应）
            // 避免问题：若此处直接 write 响应流，后续过滤器/控制器将无法操作响应，导致响应格式混乱
            filterChain.doFilter(request, response);
            return;
        }

        // ===================================== Step 4：构造 Spring Security 认证对象 =====================================
        // 4.1 构造用户数据模型（UsersDO）：基于 JWT 载荷中的信息，创建最小化的用户对象
        // 设计考量：仅携带后续流程必需的字段（如 account），避免将敏感信息（如密码）放入令牌或传递到后续环节
        // 注意：若 JwtPayload 中包含用户 ID，也需在此处设置（如 userDO.setId(jwtPayload.getUserId())），供业务逻辑使用
        UsersDO userDO = new UsersDO();
        userDO.setAccount(jwtPayload.getUsername()); // 从载荷中获取用户名，匹配 SecurityUserVO 的 username 字段

        // 4.2 封装为 SecurityUserVO：适配 Spring Security 的 UserDetails 接口
        // SecurityUserVO 需实现 UserDetails 接口，提供：
        // - getUsername()：返回用户名（此处为 userDO.getAccount()）
        // - getAuthorities()：返回用户权限集合（如 ROLE_ADMIN、ROLE_USER）
        // - isAccountNonExpired()/isEnabled() 等：账号状态（默认返回 true，可根据业务扩展）
        SecurityUserVO loginUser = new SecurityUserVO(userDO);

        // ===================================== Step 5：注入认证信息到 SecurityContext =====================================
        // 5.1 创建 UsernamePasswordAuthenticationToken（Spring Security 核心认证对象）
        // 构造参数说明：
        // - principal：认证主体（即 loginUser，存储用户信息和权限）
        // - credentials：认证凭证（JWT 场景下无需存储明文密码，故设为 null）
        // - authorities：用户权限集合（从 loginUser 中获取，若为 null 则用空集合兜底，避免 NPE）
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(
                        loginUser,
                        null,
                        loginUser.getAuthorities() != null ? loginUser.getAuthorities() : Collections.emptyList()
                );

        // 5.2 将认证对象写入 SecurityContext（ThreadLocal 容器）
        // 核心作用：
        // - 后续授权逻辑（如 @PreAuthorize、authorizeHttpRequests）会从 SecurityContext 中获取认证信息
        // - 业务代码中可通过 SecurityContextHolder.getContext().getAuthentication() 获取当前登录用户
        // 线程安全：ThreadLocal 确保每个请求线程的认证信息独立，不会相互干扰
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        // ===================================== Step 6：放行请求到后续过滤器/控制器 =====================================
        // 此时请求已携带认证信息，后续流程：
        // 1. 若请求匹配需授权接口，Security 会基于 authenticationToken 中的权限进行授权判断
        // 2. 若请求匹配匿名接口，直接放行到业务控制器
        // 3. 若请求存在其他过滤器（如 XSS 过滤、日志记录），会继续执行对应的过滤逻辑
        filterChain.doFilter(request, response);
    }
}